﻿namespace Neovolve.Bootstrapper.Wizard
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text.RegularExpressions;
    using System.Windows.Forms;
    using EnvDTE;

    /// <summary>
    /// The <see cref="PackageDefinition"/>
    ///   class is used to allow the user to select the source package.
    /// </summary>
    internal partial class PackageDefinition : Form
    {
        /// <summary>
        /// Stores the deployment projects available in the current solution.
        /// </summary>
        private readonly IEnumerable<Project> _deploymentProjects;

        /// <summary>
        /// Stores the project path.
        /// </summary>
        private readonly String _projectPath;

        /// <summary>
        /// Stores whether the current solution is saved.
        /// </summary>
        private readonly Boolean _solutionIsSaved;

        /// <summary>
        /// Stores the value of the <see cref="PackagePath"/> property.
        /// </summary>
        private String _packagePath;

        /// <summary>
        /// Initializes a new instance of the <see cref="PackageDefinition"/> class.
        /// </summary>
        /// <param name="dte">
        /// The DTE.
        /// </param>
        /// <param name="projectPath">
        /// The project path.
        /// </param>
        public PackageDefinition(DTE dte, String projectPath)
        {
            if (dte == null)
            {
                throw new ArgumentNullException("dte");
            }

            if (String.IsNullOrWhiteSpace(projectPath))
            {
                throw new ArgumentNullException("projectPath");
            }

            _projectPath = projectPath;

            InitializeComponent();

            if (dte.Solution.Projects.Count > 0)
            {
                _solutionIsSaved = true;
            }
            else if (dte.IsSavingProjectWhenCreated())
            {
                _solutionIsSaved = true;
            }
            else
            {
                _solutionIsSaved = false;
            }

            _deploymentProjects = dte.DeploymentProjects();
            IList<String> availableProjects = (from x in _deploymentProjects
                                               select x.Name).ToList();
            Boolean allowProjectSelection = availableProjects.Any();

            UseExistingProject.Enabled = allowProjectSelection;
            UseExistingProject.Checked = allowProjectSelection;
            ProjectNames.Enabled = allowProjectSelection;
            UseExistingFile.Checked = UseExistingProject.Checked == false;
            SelectPackage.Enabled = UseExistingFile.Checked;

            ProjectNames.DataSource = availableProjects;
        }

        /// <summary>
        /// Calculates from path.
        /// </summary>
        /// <param name="fileName">
        /// Name of the file.
        /// </param>
        /// <param name="projectPath">
        /// The project path.
        /// </param>
        /// <param name="solutionIsSaved">
        /// <c>true</c> if the solution is saved; otherwise, <c>false</c>.
        /// </param>
        /// <param name="substituteOutputPath">
        /// <c>true</c> if substituting the output path; otherwise, <c>false</c>.
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance.
        /// </returns>
        private static String CalculateFromPath(String fileName, String projectPath, Boolean solutionIsSaved, Boolean substituteOutputPath)
        {
            if (String.IsNullOrWhiteSpace(fileName))
            {
                return String.Empty;
            }

            String packagePath = fileName;

            if (solutionIsSaved)
            {
                // Make the path relative to the project
                packagePath = GetRelativePath(projectPath, fileName);
            }

            Regex expression = new Regex(@"bin\\(Release|Debug)\\", RegexOptions.Singleline);

            if (substituteOutputPath)
            {
                return expression.Replace(packagePath, "$(OutputPath)");
            }

            return packagePath;
        }

        /// <summary>
        /// Gets the relative path.
        /// </summary>
        /// <param name="source">
        /// The source.
        /// </param>
        /// <param name="target">
        /// The target.
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance.
        /// </returns>
        private static String GetRelativePath(String source, String target)
        {
            // Require trailing backslash for path
            if (source.EndsWith("\\", StringComparison.OrdinalIgnoreCase) == false)
            {
                source += "\\";
            }

            Uri baseUri = new Uri(source);
            Uri fullUri = new Uri(target);

            Uri relativeUri = baseUri.MakeRelativeUri(fullUri);

            // Uri's use forward slashes so convert back to backward slashes
            return relativeUri.ToString().Replace("/", "\\").Replace("%20", " ");
        }

        /// <summary>
        /// Calculates from project.
        /// </summary>
        /// <param name="projectPath">
        /// The project path.
        /// </param>
        /// <param name="projectName">
        /// Name of the project.
        /// </param>
        /// <param name="substituteOutputPath">
        /// <c>true</c> if substituting the output path; otherwise, <c>false</c>.
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance.
        /// </returns>
        private String CalculateFromProject(String projectPath, String projectName, Boolean substituteOutputPath)
        {
            if (String.IsNullOrWhiteSpace(projectName))
            {
                // There is no selected project
                return String.Empty;
            }

            Project selectProject = _deploymentProjects.Single(x => x.Name == projectName);
            String outputPath = selectProject.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString();
            String outputFullName = selectProject.GetOutputFullName();

            String relativePath = GetRelativePath(projectPath, outputFullName);

            if (substituteOutputPath)
            {
                return relativePath.Replace(outputPath, "$(OutputPath)");
            }

            return relativePath;
        }

        /// <summary>
        /// Handles the Click event of the Ok control.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> instance containing the event data.
        /// </param>
        private void Ok_Click(Object sender, EventArgs e)
        {
            if (UseExistingProject.Checked)
            {
                SelectedProject = ProjectNames.Text;
            }
        }

        /// <summary>
        /// The package definition_ form closing.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void PackageDefinition_FormClosing(Object sender, FormClosingEventArgs e)
        {
            if (DialogResult != DialogResult.OK)
            {
                return;
            }

            if (String.IsNullOrWhiteSpace(PackagePath))
            {
                MessageBox.Show("You must select a package to embed in the project.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

                e.Cancel = true;
            }
        }

        /// <summary>
        /// Handles the SelectedIndexChanged event of the ProjectNames control.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> instance containing the event data.
        /// </param>
        private void ProjectNames_SelectedIndexChanged(Object sender, EventArgs e)
        {
            UpdatePackagePath();
        }

        /// <summary>
        /// Handles the Click event of the SelectPackage control.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> instance containing the event data.
        /// </param>
        private void SelectPackage_Click(Object sender, EventArgs e)
        {
            using (OpenFileDialog dialog = new OpenFileDialog())
            {
                dialog.AutoUpgradeEnabled = true;
                dialog.CheckFileExists = true;
                dialog.CheckPathExists = true;
                dialog.DefaultExt = ".*";
                dialog.DereferenceLinks = true;
                dialog.Filter = "Windows Installer Packages (*.msi)|*.msi|Executables (*.exe)|*.exe|All files (*.*)|*.*";
                dialog.FilterIndex = 0;
                dialog.Multiselect = false;
                dialog.Title = "Select package to bootstrap";

                DialogResult dialogResult = dialog.ShowDialog(this);

                if (dialogResult == DialogResult.OK)
                {
                    SelectedPackageFile = dialog.FileName;

                    UpdatePackagePath();
                }
            }
        }

        /// <summary>
        /// Handles the CheckedChanged event of the SubstituteOutputPath control.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> instance containing the event data.
        /// </param>
        private void SubstituteOutputPath_CheckedChanged(Object sender, EventArgs e)
        {
            UpdatePackagePath();
        }

        /// <summary>
        /// Updates the package path.
        /// </summary>
        private void UpdatePackagePath()
        {
            if (UseExistingProject.Checked)
            {
                PackagePath = CalculateFromProject(_projectPath, ProjectNames.Text, SubstituteOutputPath.Checked);
            }
            else
            {
                PackagePath = CalculateFromPath(SelectedPackageFile, _projectPath, _solutionIsSaved, SubstituteOutputPath.Checked);
            }
        }

        /// <summary>
        /// Handles the CheckedChanged event of the UseExistingFile control.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> instance containing the event data.
        /// </param>
        private void UseExistingFile_CheckedChanged(Object sender, EventArgs e)
        {
            SelectPackage.Enabled = UseExistingFile.Checked;

            UpdatePackagePath();
        }

        /// <summary>
        /// Handles the CheckedChanged event of the UseExistingProject control.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> instance containing the event data.
        /// </param>
        private void UseExistingProject_CheckedChanged(Object sender, EventArgs e)
        {
            ProjectNames.Enabled = UseExistingProject.Checked;

            UpdatePackagePath();
        }

        /// <summary>
        /// Gets the package path.
        /// </summary>
        public String PackagePath
        {
            get
            {
                return _packagePath;
            }

            private set
            {
                _packagePath = value;

                EmbeddedPackage.Text = value;
            }
        }

        /// <summary>
        /// Gets the selected project.
        /// </summary>
        public String SelectedProject
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the selected package file.
        /// </summary>
        /// <value>
        /// The selected package file.
        /// </value>
        private String SelectedPackageFile
        {
            get;
            set;
        }
    }
}